package top.raoqq.sky.service.impl;

import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.generator.MathGenerator;
import cn.hutool.captcha.generator.RandomGenerator;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import top.raoqq.sky.constant.*;
import top.raoqq.sky.dto.CaptchaDTO;
import top.raoqq.sky.dto.LoginUserDTO;
import top.raoqq.sky.dto.MenuDTO;
import top.raoqq.sky.entity.sql.Config;
import top.raoqq.sky.entity.sql.Menu;
import top.raoqq.sky.entity.sql.User;
import top.raoqq.sky.enums.SkyResponseEnum;
import top.raoqq.sky.exception.BizException;
import top.raoqq.sky.mapper.MenuMapper;
import top.raoqq.sky.mapper.UserMapper;
import top.raoqq.sky.service.CommonService;
import top.raoqq.sky.service.ConfigService;
import top.raoqq.sky.util.CaptchaUtils;
import top.raoqq.sky.util.RedisUtils;
import top.raoqq.sky.util.StringUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author 饶奇奇
 * @date 2023/8/4
 */
@Service
@RequiredArgsConstructor
public class CommonServiceImpl implements CommonService, UserDetailsService {
    private final ConfigService configService;
    private final RedisUtils redisUtil;
    private final UserMapper userMapper;
    private final PasswordEncoder passwordEncoder;
    private final MenuMapper menuMapper;


    @Override
    public CaptchaDTO genCaptcha() {
        CaptchaDTO captchaDTO = new CaptchaDTO();
        boolean captchaEnable = configService.enableCaptcha();
        captchaDTO.setCaptchaEnable(captchaEnable);
        if (captchaEnable) {
            // 1.获取验证码的类型
            LambdaQueryWrapper<Config> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(Config::getConfigKey, ConfigKeyConstant.CAPTCHA_TYPE);
            Config config = configService.getOne(wrapper);
            // 用户是否设置验证码
            boolean userIsSetCaptchaType = Objects.nonNull(config) &&  (ConfigValueConstant.CAPTCHA_CHAR.equals(config.getConfigValue()) || ConfigValueConstant.CAPTCHA_MATH.equals(config.getConfigValue()));
            // 验证码类型
            String captchaType = "";
            if (!userIsSetCaptchaType) {
                // 没有设置验证码，验证码类型为默认值
                captchaType = ConfigValueConstant.CAPTCHA_DEFAULT;
            } else {
                // 验证码类型就是用户设置的值
                captchaType = config.getConfigValue();
            }
            AbstractCaptcha captcha = CaptchaUtil.createLineCaptcha(80, 30);
            // 验证码结果
            String code = "";
            // 2.生成验证码
            if (ConfigValueConstant.CAPTCHA_MATH.equals(captchaType)) {
                captcha.setGenerator(new MathGenerator());
                captcha.createCode();
                code = CaptchaUtils.getMathResult(captcha.getCode());
            } else {
                // 自定义纯数字的验证码（随机4位数字，可重复）
                RandomGenerator randomGenerator = new RandomGenerator("0123456789", 4);
                captcha.setGenerator(randomGenerator);
                // 重新生成code
                captcha.createCode();
                code = captcha.getCode();
            }
            // 验证码密钥
            String uuid = IdUtil.simpleUUID();
            // 3.将验证码存入redis中
            // 加上前缀
            String key = RedisCacheKeyConstant.CAPTCHA_KEY + uuid;
            redisUtil.setCache(key, code, SkyConstant.CAPTCHA_EXPIRE, TimeUnit.SECONDS);
            captchaDTO.setUuid(uuid);
            // 3.将验证码的图片字节数组进行Base64位编码
            captchaDTO.setImg(captcha.getImageBase64());
        } else {
            captchaDTO.setUuid("");
            captchaDTO.setImg("");
        }
        return captchaDTO;
    }

    @Override
    public void validateCaptcha(String uuid, String code) {
        // 验证码参数校验
        if (StringUtils.isBlank(uuid) || StringUtils.isBlank(code)) {
            throw new BizException(SkyResponseEnum.CAPTCHA_ERROR);
        }
        String captchaKey = RedisCacheKeyConstant.CAPTCHA_KEY + uuid;
        // 不存在说明过期或者是伪造的key
        if (!redisUtil.hasKey(captchaKey)) {
            throw new BizException(SkyResponseEnum.CAPTCHA_EXPIRE);
        }
        String redisCaptchaCode = redisUtil.getCache(captchaKey).toString();
        if (!code.equals(redisCaptchaCode)) {
            throw new BizException(SkyResponseEnum.CAPTCHA_ERROR);
        }

    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUsername, username);
        User user = userMapper.selectOne(wrapper);
        if (Objects.isNull(user)) {
            throw new BizException(SkyResponseEnum.USER_NOT_FOUND);
        }
        // 用户被禁用了，除了status为0其他都是禁用
        if (Objects.isNull(user.getStatus()) || user.getStatus().intValue() != 0) {
            throw new BizException(SkyResponseEnum.USER_DISABLED);
        }
        // 校验密码
        validatePassword(username, user.getPassword());
        // 获取用户权限
        List<String>  currentUserPermission = null;

        return new LoginUserDTO(username, user.getPassword(), user.getAvatar(), user.getNickname(), currentUserPermission);
    }



    public void validatePassword(String username, String userPassword) {
        String key = RedisCacheKeyConstant.LOGIN_COUNT + username;
        // 登录失败次数
        int loginCount = -1;
        // 校验用户密码输入错误次数
        if (redisUtil.hasKey(key) && Objects.nonNull(redisUtil.getCache(key))) {
            loginCount = Integer.parseInt(redisUtil.getCache(key).toString());
        } else {
            loginCount = 0;
            // 5分钟内只能输错3次
            redisUtil.setCache(key,0, SkyConstant.PASSWORD_ERROR_TIME,TimeUnit.MINUTES);
        }
        // 登录失败次数大于等于最大登录失败次数
        if (loginCount >= SkyConstant.MAX_LOGIN_FAIR_COUNT) {
            throw new BizException(SkyResponseEnum.USER_LOGIN_FAIR_OVER_MAX);
        }
        // 校验密码
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        String password = authentication.getCredentials().toString();

        // 密码不一致
        if (!passwordEncoder.matches(password,userPassword)) {
            redisUtil.incr(key);
            throw new BizException(SkyResponseEnum.USER_LOGIN_FAIR);
        }
        // 密码正确
        redisUtil.deleteCache(key);
    }

    @Override
    public List<MenuDTO> getRoutes(String username) {
        // 获取当前用户的角色字符
        Set<String> roleSet = userMapper.getRolesByUsername(username);
        List<Menu> menuList = null;
        if (!CollectionUtils.isEmpty(roleSet) && roleSet.contains(SkyConstant.ADMIN)) {
            // 超级管理员
            menuList = menuMapper.listRoutes();
        } else {
            menuList = menuMapper.getRoutesByUsername(username);
        }
        // 构造成树
        return buildMenuTree(menuList);
        
    }

    @Override
    public List<MenuDTO> buildMenuTree(List<Menu> menuList) {
        List<MenuDTO> treeMenu = new ArrayList<>();
        List<Menu> firstLevel = menuList.stream().
                filter(item -> item.getParentId().equals(0L))
                .collect(Collectors.toList());
        firstLevel.forEach(item -> {
            treeMenu.add(MenuDTO.initMenuDTO(item));
        });
        setChildren(treeMenu, menuList);
        return treeMenu;
    }

    private void setChildren(List<MenuDTO> treeMenu, List<Menu> menuList) {
        treeMenu.stream().forEach(item -> {
            List<MenuDTO> children = new ArrayList<>();
            for (Menu menu : menuList) {
                if (item.getMenuId().equals(menu.getParentId())) {
                    children.add(MenuDTO.initMenuDTO(menu));
                }
            }
            setChildren(children, menuList);
            item.setChildren(children);
        });
    }
}
